#ifndef _GALOIS_H
#define _GALOIS_H

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

/* These work with any value of w <= 32 as a parameter, but have helper functions for specific ranges of w */
uint32_t galois_single_multiply(uint32_t a, uint32_t b, int w);
uint32_t galois_single_divide(uint32_t a, uint32_t b, int w);
uint32_t galois_inverse(uint32_t x, int w);

int galois_create_log_tables(int w);   /* Returns 0 on success, -1 on failure */
uint32_t galois_logtable_multiply(uint32_t x, uint32_t y, int w);
uint32_t galois_logtable_divide(uint32_t x, uint32_t y, int w);

int galois_create_mult_tables(int w);   /* Returns 0 on success, -1 on failure */
uint32_t galois_multtable_multiply(uint32_t x, uint32_t y, int w);
uint32_t galois_multtable_divide(uint32_t x, uint32_t y, int w);

uint32_t galois_bitmatrix_multiply(uint32_t x, uint32_t y, int w);
uint32_t galois_bitmatrix_divide(uint32_t x, uint32_t y, int w);
uint32_t galois_bitmatrix_inverse(uint32_t y, int w);

uint32_t galois_shift_multiply(uint32_t x, uint32_t y, int w);
/*Do the following two implementations */
uint32_t galois_shift_divide(uint32_t x, uint32_t y, int w);
uint32_t galois_euclid_inverse(uint32_t x, int w);

uint32_t galois_log(uint32_t value, int w);
uint32_t galois_ilog(uint32_t value, int w);

/* Helper functions for specified widths */
/* w <= 8 */
int galois_w08_create_log_tables(int w);
unsigned char galois_w08_logtable_multiply(unsigned char x, unsigned char y, int w);
unsigned char galois_w08_logtable_divide(unsigned char x, unsigned char y, int w);

int galois_w08_create_mult_tables(int w);
unsigned char galois_w08_multtable_multiply(unsigned char x, unsigned char y, int w);
int galois_w08_multtable_divide(unsigned char x, unsigned char y, int w);

unsigned char galois_w08_composite_k2_multiply(unsigned char x, unsigned char y);
unsigned char galois_w08_composite_k2_divide(unsigned char x, unsigned char y);
unsigned char galois_w08_composite_k2_inverse(unsigned char x);

/* w <= 16 */
int galois_w16_create_log_tables(int w);
uint16_t galois_w16_logtable_multiply(uint16_t x, uint16_t y, int w);
uint16_t galois_w16_logtable_divide(uint16_t x, uint16_t y, int w);

int galois_w16_create_mult_tables(int w);
uint16_t galois_w16_multtable_multiply(uint16_t x, uint16_t y, int w);
uint16_t galois_w16_multtable_divide(uint16_t x, uint16_t y, int w);

uint16_t galois_w16_composite_k2_multiply(uint16_t x, uint16_t y);
uint16_t galois_w16_composite_k2_divide(uint16_t x, uint16_t y);
uint16_t galois_w16_composite_k2_inverse(uint16_t x);

/* w <= 32 */
int galois_w32_create_log_tables(int w);
uint32_t galois_w32_logtable_multiply(uint32_t x, uint32_t y, int w);
uint32_t galois_w32_logtable_divide(uint32_t x, uint32_t y, int w);

int galois_w32_create_split8_tables();
uint32_t galois_w32_split8_table_multiply(uint32_t x, uint32_t y);
/* add in a divide for split8 */

uint32_t galois_w32_composite_k2_multiply(uint32_t x, uint32_t y);
uint32_t galois_w32_composite_k2_divide(uint32_t x, uint32_t y);
uint32_t galois_w32_composite_k2_inverse(uint32_t x);

/* w == 64 */
uint64_t galois_w64_bitmatrix_multiply (uint64_t x, uint64_t y);
uint64_t galois_w64_bitmatrix_divide (uint64_t x, uint64_t y);
uint64_t galois_w64_bitmatrix_inverse (uint64_t x);

uint64_t galois_w64_shift_multiply(uint64_t x, uint64_t y);
/* add in shift divide and euclid inverse */

int galois_w64_create_split8_tables ();
uint64_t galois_w64_split8_table_multiply (uint64_t x, uint64_t y);
uint64_t galois_w64_split8_table_divide (uint64_t x, uint64_t y);

uint64_t galois_w64_composite_k2_multiply(uint64_t x, uint64_t y);
uint64_t galois_w64_composite_k2_divide(uint64_t x, uint64_t y);
uint64_t galois_w64_composite_k2_inverse(uint64_t x);

uint64_t galois_w64_recursive_composite_k2_multiply(uint64_t x, uint64_t y); /* (2^16) ^ 2 ^ 2 */
uint64_t galois_w64_recursive_composite_k2_divide(uint64_t x, uint64_t y);
uint64_t galois_w64_recursive_composite_k2_inverse(uint64_t x);

/* w == 128 */
uint64_t *galois_w128_bitmatrix_multiply (uint64_t *x, uint64_t *y);
uint64_t *galois_w128_bitmatrix_divide (uint64_t *x, uint64_t *y);
uint64_t *galois_w128_bitmatrix_inverse (uint64_t *x);

int galois_w128_create_split8_tables();
uint64_t *galois_w128_split8_table_multiply(uint64_t *x, uint64_t *y);
/* divide */

uint64_t *galois_w128_shift_multiply(uint64_t *x, uint64_t *y);
/* divide and euclid inverse */

uint64_t *galois_w128_composite_k2_multiply(uint64_t *x, uint64_t *y);
uint64_t *galois_w128_composite_k2_divide(uint64_t *x, uint64_t *y);
uint64_t *galois_w128_composite_k2_inverse(uint64_t *x);

uint64_t *galois_w128_recursive_composite_k2_multiply(uint64_t *x, uint64_t *y);
uint64_t *galois_w128_recursive_composite_k2_divide(uint64_t *x, uint64_t *y);
uint64_t *galois_w128_recursive_composite_k2_inverse(uint64_t *x);

/* Ease of access */
void *galois_get_mult_table(int w);
void *galois_get_div_table(int w);
void *galois_get_log_table(int w);
void *galois_get_ilog_table(int w);
uint32_t **galois_get_split8_tables();

/* These multiply regions in w=4, w=8, w=16, w=32, w=64, and w=128.  The regions must be
   aligned on 8 bytes. */

/*  w=4 implementations:
	full mult tables
	lazy multtable
	shift
	bitmatrix
	log/antilog
*/

void galois_w04_multtable_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,  
		int add);

void galois_w04_lazytable_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w04_shift_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2, 
		int add);

void galois_w04_bitmatrix_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2, 
		int add);

void galois_w04_logtable_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

/*  w=8 implementations:
	full mult tables
	lazy multtable (only one row in memory at a time)
	shift
	bitmatrix
	log/antilog
	composite k=2 */
	
void galois_w08_multtable_region_multiply(unsigned char *region,       /* Region to multiply */
		int multby,       			/* Number to multiply by */
		int nbytes,     			/* Number of bytes in region */
		unsigned char *r2,         	/* If r2 != NULL, products go here.  
										Otherwise region is overwritten */
		int add);         			/* If (r2 != NULL && add) the produce is XOR'd with r2 */

void galois_w08_lazytable_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);
		
void galois_w08_shift_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);
		
void galois_w08_bitmatrix_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w08_logtable_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w08_composite_k2_region_multiply (unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

/*  w == 16 implementations:
	log/antilog
	lazy multtable
	shift
	bitmatrix
	composite k == 2
	split composite k == 2 */
	
void galois_w16_logtable_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w16_lazytable_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);
		
void galois_w16_shift_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);
		
void galois_w16_bitmatrix_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w16_composite_k2_region_multiply (unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w16_split_composite_k2_region_multiply (unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

/*  w == 32 implementations:
	split w8 tables
	shift
	bitmatrix
	lazy split w8 tables
	lazy split w16 tables
	composite k == 2
	split composite k == 2 */

void galois_w32_split8_table_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w32_shift_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);
		
void galois_w32_bitmatrix_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);
			
void galois_w32_lazy_split8_table_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);
		
void galois_w32_lazy_split16_table_region_multiply(unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

/* This needs work, inlining */
void galois_w32_composite_k2_region_multiply (unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w32_split_composite_k2_region_multiply (unsigned char *region,
		int multby,
		int nbytes,
		unsigned char *r2,
		int add);

/*  w == 64 implementations:
	split w8 tables
	lazy split w8 tables
	lazy split w16 tables
	shift
	bitmatrix
	composite k == 2  (using split w8 for l == 32)
	split composite k == 2 
	recursive composite k == 2 (calls split composite k==2) */

void galois_w64_split8_table_region_multiply (unsigned char *region,
		uint64_t multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w64_lazy_split8_table_region_multiply (unsigned char *region,
		uint64_t multby,
		int nbytes,
		unsigned char *r2,
		int add);
		
void galois_w64_lazy_split16_table_region_multiply (unsigned char *region,
		uint64_t multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w64_shift_region_multiply (unsigned char *region,
		uint64_t multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w64_bitmatrix_region_multiply (unsigned char *region,
		uint64_t multby,
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w64_composite_k2_region_multiply (unsigned char *region,
		uint64_t multby,
		int nbytes,
		unsigned char *r2,
		int add);
		
void galois_w64_split_composite_k2_region_multiply (unsigned char *region,
		uint64_t multby,
		int nbytes,											/* must be a multiply of 16 */
		unsigned char *r2,
		int add);

void galois_w64_recursive_composite_k2_region_multiply (unsigned char *region,
		uint64_t multby,
		int nbytes,											/* must be a multiply of 32 */
		unsigned char *r2,
		int add);

/*  w == 128
	shift
	bitmatrix
	split w8 tables
	lazy split w8 tables
	composite k == 2 (using split table for l == 64)
	split composite k == 2
	recursive composite k == 2 (calling galois_w64_recursive_composite_k2_RM) */
	
void galois_w128_shift_region_multiply (unsigned char *region,
		uint64_t multby[2],
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w128_bitmatrix_region_multiply (unsigned char *region,
		uint64_t multby[2],
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w128_split8_table_region_multiply (unsigned char *region,
		uint64_t multby[2],
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w128_lazy_split8_table_region_multiply (unsigned char *region,
		uint64_t multby[2],
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w128_lazy_split16_table_region_multiply (unsigned char *region,
		uint64_t multby[2],
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w128_composite_k2_region_multiply (unsigned char *region,
		uint64_t multby[2],
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w128_split_composite_k2_region_multiply (unsigned char *region,
		uint64_t multby[2],
		int nbytes,
		unsigned char *r2,
		int add);

void galois_w128_recursive_composite_k2_region_multiply (unsigned char *region,
		uint64_t multby[2],
		int nbytes,
		unsigned char *r2,
		int add);

/* Functions that do not directly depend upon another galois implementation */
void galois_region_xor(		unsigned char *r1,         /* Region 1 */
							unsigned char *r2,         /* Region 2 */
							unsigned char *r3,         /* Sum region (r3 = r1 ^ r2) -- can be r1 or r2 */
							int nbytes);      /* Number of bytes in region */

void reed_sol_galois_w08_region_multby_2(unsigned char *region, int nbytes);
void reed_sol_galois_w16_region_multby_2(unsigned char *region, int nbytes);
void reed_sol_galois_w32_region_multby_2(unsigned char *region, int nbytes);

#endif
